package com.mjk.common.core.jdbc.ctable;


import cn.hutool.core.exceptions.ExceptionUtil;
import com.mjk.common.base.model.BaseEntity;
import com.mjk.common.core.jdbc.annotation.ArcSharding;
import com.mjk.common.core.jdbc.datasource.IDataSource;
import com.mjk.common.core.jdbc.service.plus.provider.BeeDataSourceType;
import com.mjk.common.core.jdbc.service.plus.provider.interfaces.IBeeProvider;
import com.mjk.common.core.jdbc.sphere.impl.table.CapacityPreciseShardingAlgorithm;
import com.mjk.common.core.jdbc.util.ShardingUtils;
import com.mjk.common.core.jdbc.util.TableUtils;
import com.mjk.common.core.jdbc.util.provider.SqlProvider;
import com.mjk.common.tools.logger.BizLogger;
import com.mjk.common.util.ObjectUtils;
import com.mjk.common.util.StrUtils;
import org.apache.shardingsphere.shardingjdbc.jdbc.core.datasource.ShardingDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationListener;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.rowset.SqlRowSet;
import org.springframework.jdbc.support.rowset.SqlRowSetMetaData;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.sql.DataSource;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.util.*;

/**
 *
 */
@Service
public class ShardingTableService implements EnvironmentAware,ApplicationListener<ContextRefreshedEvent> {
    private final IDataSource dataSource;

    private final Map<String,JdbcTemplate> templateMap = new HashMap<>();
    
    private Environment environment;
  
	@Value("${sharding.jdbc.datasource.defaultname:his}")
	private String WRITE_BASESOURCE_NAME;
	/**
	 * 是否可以对字段进行删除 默认关闭
	 */
	@Value("${sharding.jdbc.isdrop:false}")
	private boolean isDrop;

	public ShardingTableService(IDataSource dataSource) {
		this.dataSource = dataSource;
	}

	/**
     * 创建model类
     */
    public void createModel(BaseEntity o) {
        ArcSharding sharding = ShardingUtils.getArcSharding(o);
		if(null == sharding){
			return;
		}
    	BeeDataSourceType type = BeeDataSourceType.getDataSoureType(TableUtils.getDriverName(o,WRITE_BASESOURCE_NAME));
		if(ObjectUtils.isEmpty(type)){
			return;
		}
        String modelStr = "";
		try (Connection connection =dataSource.getDataSource(sharding).getConnection()){
			DatabaseMetaData metaData = connection.getMetaData();
            ResultSet rs = null;
            if (type.getDriver().equals(BeeDataSourceType.SQLSERVER_WRAP.getDriver())
                    || type.getDriver().equals(BeeDataSourceType.SQLSERVER1_WRAP.getDriver())) {
                rs = metaData.getColumns(sharding.userName(), null, sharding.table(), null);
            } else {
                rs = metaData.getColumns(null, null, sharding.table(), null);
            }
            modelStr = ShardingUtils.getModel(rs, o, sharding);
		}catch (Exception e){
			BizLogger.error(e);
		}
		if (ObjectUtils.isNotEmpty(modelStr)) {
			String filepath = (Objects.requireNonNull(o.getClass().getResource("")).getPath());
			String entityName = o.getClass().getName();
			String fileName = entityName.substring(entityName.lastIndexOf(46) + 1) + ".java";
			fileName = filepath + fileName;
			if (fileName.contains("target/classes/")) {
				filepath = fileName.replace("target/classes/", "src/main/java/");
			}
			if (fileName.contains("target/test-classes/")) {
				filepath = fileName.replace("target/test-classes/", "src/test/java/");
			}
			File f = new File(filepath);
			try (FileWriter fw = new FileWriter(f);) {
				fw.write(modelStr);
			} catch (IOException e) {
				BizLogger.error(e);
			}
		}
    }
    
	public void createTable(BaseEntity o) {
        ArcSharding sharding = ShardingUtils.getArcSharding(o);
		if(null == sharding){
			return;
		}
		BeeDataSourceType type = BeeDataSourceType.getDataSoureType(TableUtils.getDriverName(o,WRITE_BASESOURCE_NAME));
		if(ObjectUtils.isEmpty(type)){
			return;
		}
		String createSql = type.getHandlerInstance().getCreateTable(o);			
		if(ObjectUtils.isEmpty(createSql)) {
			return;
		}
		DataSource ds = dataSource.getJdbcTemplate(sharding).getDataSource();
        if(ds instanceof ShardingDataSource) {
            ShardingInfo shardingInfo = CapacityPreciseShardingAlgorithm.sharding(sharding);
            for (String tabName : shardingInfo.getShardingList()) {
                try {
                	dataSource.getJdbcTemplate(sharding).execute(String.format(createSql, tabName));
                } catch (Exception e) {
                    alartSql(o, tabName,type);
                }
            }
        }else {
        	String tabName = type.getHandlerInstance().getTabName(sharding);
            try {
            	dataSource.getJdbcTemplate(sharding).execute(String.format(createSql, tabName));
                //如果是ORACEL 给表创建主键生成 
                //if(TableUtils.getDriverName(o).contains(BeeDataSourceType.ORACLE.getType())){
                //	type.getHandlerInstance().initPramaryIncrement(o);
                //}
            }catch (Exception e){
				alartSql(o, tabName,type);
				String errorMsg = ExceptionUtil.getMessage(e);
            	if(ObjectUtils.isNotEmpty(errorMsg) &&!errorMsg.contains("already exists") ) {
            		//不是包含表已经存储在的错误打印
					BizLogger.error("error sql:" + String.format(createSql, tabName) + "reason:" + ExceptionUtil.getMessage(e));
            	}
            }
        }
        
	}

	public void delTable(String tableName) {
		List<JdbcTemplate> templates = dataSource.getAllJdbcTemplate();
		if(CollectionUtils.isEmpty(templates)) {
			return;
		}
        for(JdbcTemplate jdbcTemplate1 : templates) {
            jdbcTemplate1.execute("DROP TABLE " + tableName);
        }
	}

    /**
     * 根据model的字段，判断是否需要alert数据库字段
     */
	public void alartSql(BaseEntity o, String tabName, BeeDataSourceType type) {
        ArcSharding sharding = ShardingUtils.getArcSharding(o);
		IBeeProvider handler = type.getHandlerInstance();
		String sql = handler.getTableStruct(tabName);
		SqlRowSet sqlRowSet = dataSource.getJdbcTemplate(sharding).queryForRowSet(sql);
		SqlRowSetMetaData sqlRsmd = sqlRowSet.getMetaData();
		StringBuilder alertSB = new StringBuilder();
		int columnCount = sqlRsmd.getColumnCount();
        List<Field> fields = SqlProvider.getValidTableFiled(o);
		if (fields.size() < columnCount && isDrop) {
			alertSB.append("alter table ").append(tabName);
			List<String> dropNames = new ArrayList<>();
			for (int i = 1; i <= columnCount; i++) {
				int needDropAlert = 0;
				String alertDropName = "";
				for (Field f : fields) {
					String fName = f.getName();
					if (sqlRsmd.getColumnName(i).equalsIgnoreCase(StrUtils.humpToUnderline(fName,sharding.hump()))) {
						needDropAlert++;
					} else {
						alertDropName = sqlRsmd.getColumnName(i);
					}
				}
				if (needDropAlert == 0)
					dropNames.add(alertDropName);
			}
			alertSB.append(handler.getAlertDrop(dropNames));
		} else if (fields.size() > columnCount) {
			alertSB.append("alter table ").append(tabName);
			for (Field f : fields) {
				int needAddAlert = 0;
				for (int i = 1; i <= columnCount; i++) {
					if (!StrUtils.humpToUnderline(f.getName(),sharding.hump()).equalsIgnoreCase(sqlRsmd.getColumnName(i))) {
						needAddAlert++;
					}
				}
				if (needAddAlert == columnCount) {
					//Filed filed = f.getAnnotation(Filed.class);
					//if (filed != null) {
						handler.createSQL(alertSB.append(" add "), f, type, true);
					//}else {
					//	handler.createSQL(alertSB.append(" add "), f,type,true);
					//}
				}
			}
		}
		String alartSql = alertSB.toString();
		if (ObjectUtils.isNotEmpty(alartSql)) {
			if (alartSql.endsWith(",")) {
				alartSql = alartSql.substring(0, alartSql.length() - 1);
			}
			try {
				dataSource.getJdbcTemplate(sharding).execute(alartSql);
			}catch(Exception e){
				BizLogger.info("Executing SQL statement [" + alartSql + "]" + ExceptionUtil.getMessage(e));
			}
		}
    }

	
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        for(Map.Entry<String,DataSource> entry : event.getApplicationContext().getBeansOfType(DataSource.class).entrySet()){
            templateMap.put(entry.getKey(),new JdbcTemplate(entry.getValue()));
        }
    }
	@Override
	public void setEnvironment(Environment environment) {
		this.environment = environment;
	}
	/**
	 * 获取默认的数据库名称
     */
	public String getDefaultDataSourceName() {
		String prefix = "sharding.jdbc.datasource.defaultname";
		return environment.getProperty(prefix);
	}
}
